﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace Tzync
{
    /******************************************************************************************************************/
    /* Partial Class to Analyze the Guest System's files and its LogFile and find Conflicting/Non-Conflicting Updates */
    /******************************************************************************************************************/

    partial class Analyze
    {

        //Description     : Given a list of folders and corresponding log entry TreeNodes, this function computes
        // the differences between them by recursively calling findDiff() method.        
        //Pre-Conditions  : sysDirectories, directoryChilds are not NULL, nodeParent is proper.
        //Post-Conditions : The list of differences maintained in this class is updated with differences in these
        // set of files.        

        private void findFolderDifference(DirectoryInfo[] sysDirectories, List<int> directoryChilds, int nodeParent)
        {
            if (sysDirectories == null) throw new ArgumentNullException("sysDirectories");
            if (directoryChilds == null) throw new ArgumentNullException("directoryChilds");


            Pair<DirectionType, OperationType>[] folderStatus = new Pair<DirectionType, OperationType>[sysDirectories.Count()];
            int sysSize = sysDirectories.Count(), partnerSize = directoryChilds.Count(), partnerIndex;
            List<String> nodeDirectoryNames = new List<string>();
            bool[] partnerFolderVisited = new bool[directoryChilds.Count()];
            DateTimeParser dtpFile;
            int vA_f, vA_s, vB_f, vB_s;

            for (int i = 0; i < sysSize; i++)
            { folderStatus[i] = new Pair<DirectionType, OperationType>(); }
            for (int i = 0; i < partnerSize; i++)
            { partnerFolderVisited[i] = false; nodeDirectoryNames.Add(arrNode[directoryChilds[i]].getName()); }

            for (int i = 0; i < sysSize; i++)
            {
                partnerIndex = nodeDirectoryNames.IndexOf(sysDirectories[i].Name);                                 
                               
                if (partnerIndex >= 0)
                {
                    vA_f = arrNode[directoryChilds[partnerIndex]].getVersionA().First;
                    vA_s = arrNode[directoryChilds[partnerIndex]].getVersionA().Second;
                    vB_f = arrNode[directoryChilds[partnerIndex]].getVersionB().First;
                    vB_s = arrNode[directoryChilds[partnerIndex]].getVersionB().Second;

                    partnerFolderVisited[partnerIndex] = true;
                    dtpFile = new DateTimeParser(sysDirectories[i].LastWriteTime);

                    //If folder is found in system, the following region checks for appropriate set of differences

                    #region CheckFolderDifferencesRegion1

                    if (systemName == SystemType.Host)
                    {
                        arrNode[directoryChilds[partnerIndex]].setFullPathA(sysDirectories[i].FullName);
                        
                        //If system file is dirty                            
                        if (DateTime.Compare(dtpFile.GetNewDateTime(), arrNode[directoryChilds[partnerIndex]].getSyncA()) != 0)
                        {
                            //If partner file is also dirty -- CONFLICTING UPDATE
                            if (vA_s != vB_s)
                            {
                                folderStatus[i].First = DirectionType.None;
                                //Detecting Modification(Here)-Deletion(There) Conflict
                                if (vB_f == 0 && vB_s == -1)
                                    folderStatus[i].Second = OperationType.ModDeleteConflict;
                                
                                //Detecting Creation-Creation Conflict
                                else if (vB_f == 0 && vB_s == 1 && vA_f == 0 && vA_s == 0)
                                    folderStatus[i].Second = OperationType.CreationCreationConflict;
                                
                                //Detecting Mod-Mod Conflict 
                                else folderStatus[i].Second = OperationType.ModModConflict;

                                AddDifference(new Difference(sysDirectories[i], arrNode[directoryChilds[partnerIndex]], folderStatus[i]));
                            }
                            
                            //If its an update only in this system -- NON-CONFLICTING UPDATE FROM THIS SIDE
                            else
                            {
                                if (filter.isValidPath(sysDirectories[i].FullName))
                                {
                                    folderStatus[i].First = DirectionType.SystemToHD;
                                    folderStatus[i].Second = OperationType.Modification;
                                    AddDifference(new Difference(sysDirectories[i], arrNode[directoryChilds[partnerIndex]],
                                                             folderStatus[i]));
                                }
                                else continue;
                            }                             
                        }

                        //Else if only partner file might be dirty 
                        else
                        {
                            //Check if partner File is dirty! -- NON-CONFLICTING UPDATE FROM PARTNER SIDE
                            if (vA_s != vB_s)
                            {
                                folderStatus[i].First = DirectionType.HDToSystem;
                                if (vB_s != -1)
                                {
                                    folderStatus[i].Second = OperationType.Modification;
                                }
                                else
                                {
                                    folderStatus[i].Second = OperationType.Deletion;
                                }
                                AddDifference(new Difference(sysDirectories[i], arrNode[directoryChilds[partnerIndex]], folderStatus[i]));
                            }
                            
                            //Else Identical files.
                            else
                            {
                                folderStatus[i].First = DirectionType.None;
                                folderStatus[i].Second = OperationType.None;
                            }
                        }

                        findDiff(sysDirectories[i], arrNode[directoryChilds[partnerIndex]]);
                    }

                    else if (systemName == SystemType.Guest)
                    {
                        arrNode[directoryChilds[partnerIndex]].setFullPathB(sysDirectories[i].FullName);

                        //If this is the first time in System B
                        if (secondTime == true)
                        {
                            folderStatus[i].First = DirectionType.None;
                            folderStatus[i].Second = OperationType.None;

                            arrNode[directoryChilds[partnerIndex]].getVersionB().First = arrNode[directoryChilds[partnerIndex]].getVersionA().First;
                            arrNode[directoryChilds[partnerIndex]].getVersionB().Second = arrNode[directoryChilds[partnerIndex]].getVersionA().Second;
                            findDiff(sysDirectories[i], arrNode[directoryChilds[partnerIndex]]);
                            continue;
                        }

                        //If system file is dirty                            
                        if (DateTime.Compare(dtpFile.GetNewDateTime(), arrNode[directoryChilds[partnerIndex]].getSyncB()) != 0)
                        {
                            
                            //If partner file is also dirty -- CONFLICTING UPDATE
                            if (vA_f != vB_f)
                            {
                                folderStatus[i].First = DirectionType.None;
                                
                                //Detecting Modification(Here)-Deletion(There) Conflict
                                if (vA_f == -1 && vA_s == 0)
                                    folderStatus[i].Second = OperationType.ModDeleteConflict;
                                
                                //Detecting Creation-Creation Conflict
                                else if (vA_f == 1 && vB_s == 0 && vB_f == 0 && vB_s == 0)
                                    folderStatus[i].Second = OperationType.CreationCreationConflict;
                                
                                //Detecting Mod-Mod Conflict 
                                else folderStatus[i].Second = OperationType.ModModConflict;

                                AddDifference(new Difference(sysDirectories[i], arrNode[directoryChilds[partnerIndex]], folderStatus[i]));
                            }
                            
                            //If its an update only in this system -- NON-CONFLICTING UPDATE FROM THIS SIDE
                            else
                            {
                                if (filter.isValidPath(sysDirectories[i].FullName))
                                {
                                    folderStatus[i].First = DirectionType.SystemToHD;
                                    folderStatus[i].Second = OperationType.Modification;
                                    AddDifference(new Difference(sysDirectories[i], arrNode[directoryChilds[partnerIndex]], folderStatus[i]));
                                }
                                else
                                {
                                    continue;
                                }
                            }
                            
                        }

                        //Else if only partner file might be dirty 
                        else
                        {
                            //Check if partner File is dirty! -- NON-CONFLICTING UPDATE FROM PARTNER SIDE
                            if (vA_f != vB_f)
                            {
                                folderStatus[i].First = DirectionType.HDToSystem;
                                if (vA_f != -1)
                                {
                                    folderStatus[i].Second = OperationType.Modification;
                                }
                                else
                                {
                                    folderStatus[i].Second = OperationType.Deletion;
                                }
                                AddDifference(new Difference(sysDirectories[i], arrNode[directoryChilds[partnerIndex]], folderStatus[i]));
                            }
                            //Else Identical files.
                            else
                            {
                                folderStatus[i].First = DirectionType.None;
                                folderStatus[i].Second = OperationType.None;
                            }
                        }

                        findDiff(sysDirectories[i], arrNode[directoryChilds[partnerIndex]]);
                    }
                    #endregion
                }                

                //NON-CONFLICTING CREATION IN THIS SIDE
                if (partnerIndex < 0 && filter.isValidPath(sysDirectories[i].FullName))
                {
                    TreeNode N;
                    try
                    {
                        N = treeHandler.folderUpdateArrNode(sysDirectories[i], nodeParent);
                    }
                    //Stack tracing
                    catch (Exception)
                    {
                        throw;
                    }

                    folderStatus[i].First = DirectionType.SystemToHD; folderStatus[i].Second = OperationType.Creation;
                    AddDifference(new Difference(sysDirectories[i], N, folderStatus[i]));
                    findDiff(sysDirectories[i], N);
                }

            }

            //CHECK FOR (DELETIONS HERE)/(CREATIONS IN PARTNER SIDE)
            for (int i = 0; i < partnerSize; i++)
            {
                Pair<DirectionType, OperationType> f = new Pair<DirectionType, OperationType>();
                vA_f = arrNode[directoryChilds[i]].getVersionA().First;
                vA_s = arrNode[directoryChilds[i]].getVersionA().Second;
                vB_f = arrNode[directoryChilds[i]].getVersionB().First;
                vB_s = arrNode[directoryChilds[i]].getVersionB().Second;

                //If the folder is not found in this system, the following function checks for appropriate set of differences

                #region CheckFolderDifferenceRegion2

                if (partnerFolderVisited[i] == false && directoryChilds[i] != -1 && systemName == SystemType.Host)
                {
                    //NON-CONFLICTING CREATION IN THE PARTNER SIDE
                    if (vA_f == 0 && vA_s == 0)
                    {
                        f.First = DirectionType.HDToSystem; f.Second = OperationType.Creation;
                        String newAddress = Path.Combine(arrNode[nodeParent].getFullPathA(), arrNode[directoryChilds[i]].getName());
                        arrNode[directoryChilds[i]].setFullPathA(newAddress);

                        AddDifference(new Difference(new DirectoryInfo(arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                        recurseDirectoryNode(arrNode[directoryChilds[i]], f, 1);
                    }
                    
                    //CONFLICTING (DELETION HERE)/UPDATE THERE
                    else if (vB_s != vA_s)
                    {
                        f.First = DirectionType.None;
                        if (vB_f == 0 && vB_s == -1)
                        {
                            if (vA_f == -1 && vA_s == 0)
                                f.Second = OperationType.None;
                            else
                            {
                                f.Second = OperationType.DeletionDeletionConflict;
                                AddDifference(new Difference(new FileInfo(@arrNode[directoryChilds[i]].getFullPathA()), arrNode[directoryChilds[i]], f));
                            }
                        }
                        
                        //If B just modified 
                        else
                        {
                            if (vA_f == -1 && vA_s == 0 && vB_f != -1 && vB_s != -1)
                            {
                                f.First = DirectionType.HDToSystem;
                                f.Second = OperationType.Modification;
                            }                            
                            else
                            {
                                f.Second = OperationType.ModDeleteConflict;
                            }
                            AddDifference(new Difference(new FileInfo(@arrNode[directoryChilds[i]].getFullPathA()), arrNode[directoryChilds[i]], f));
                        }
                    }
                    //NON-CONFLICTING DELETION HERE
                    else if (filter.isValidPath(arrNode[directoryChilds[i]].getFullPathA()))
                    {
                        //Already deleted in both places long time ago
                        if (vB_f == vA_f && vB_s == vA_s && (vA_f == -1 || vA_s == -1))
                            f.Second = OperationType.None;
                        else
                        {
                            f.Second = OperationType.Deletion;
                            f.First = DirectionType.SystemToHD;
                            AddDifference(new Difference(new DirectoryInfo(arrNode[directoryChilds[i]].getFullPathA()), arrNode[directoryChilds[i]], f));
                        }
                        recurseDirectoryNode(arrNode[directoryChilds[i]], f, 0);
                    }
                }

                else if (partnerFolderVisited[i] == false && directoryChilds[i] != -1 && systemName == SystemType.Guest)
                {
                    //NON-CONFLICTING CREATION IN THE PARTNER SIDE
                    if (vB_f == 0 && vB_s == 0)
                    {
                        f.First = DirectionType.HDToSystem; f.Second = OperationType.Creation;
                        String newAddress = Path.Combine(arrNode[nodeParent].getFullPathB(), arrNode[directoryChilds[i]].getName());
                        arrNode[directoryChilds[i]].setFullPathB(newAddress);

                        AddDifference(new Difference(new DirectoryInfo(arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                        recurseDirectoryNode(arrNode[directoryChilds[i]], f, 0);
                    }
                    //CONFLICTING (DELETION HERE)/UPDATE THERE
                    else if (vB_f != vA_f)
                    {
                        f.First = DirectionType.None;
                        //If Deletion was first detected in A (If A has also deleted)
                        if (vA_f == -1 && vA_s == 0)
                        {
                            if (vB_f == 0 && vB_s == -1)
                                f.Second = OperationType.None;
                            else
                            {
                                f.Second = OperationType.DeletionDeletionConflict;
                                AddDifference(new Difference(new FileInfo(@arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                            }
                        }
                        //If A just modified 
                        else
                        {
                            if (vB_f == 0 && vB_s == -1 && vA_f != -1 && vA_s != -1)
                            {
                                f.First = DirectionType.HDToSystem;
                                f.Second = OperationType.Modification;
                            }
                            //CHECK FOR CORRECTNESS
                            else
                            {
                                f.Second = OperationType.ModDeleteConflict;
                            }
                            AddDifference(new Difference(new FileInfo(@arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                        }
                    }
                    //NON-CONFLICTING DELETION HERE
                    else if (filter.isValidPath(arrNode[directoryChilds[i]].getFullPathB()))
                    {
                        //Already deleted in both places long time ago
                        if (vB_f == vA_f && vB_s == vA_s && (vB_f == -1 || vB_s == -1))
                            f.Second = OperationType.None;
                        else
                        {
                            f.Second = OperationType.Deletion;
                            f.First = DirectionType.SystemToHD;
                            AddDifference(new Difference(new DirectoryInfo(arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                        }
                        recurseDirectoryNode(arrNode[directoryChilds[i]], f, 1);
                    }
                }
                #endregion
            }
        }

        //Description     : In cases of Deletion in this system or creation in partner system, a need arises to recurse 
        // the tree built from metadata to mark very file in the sub-tree to be deleted/created in the
        // respective system (Host or Guest). This function does this.
        //Pre-Conditions  : N and f are not NULL and systemID is correct.
        //Post-Conditions : Non-Conflicting Creation/ Conflicting Deletion/ Non-Conflicting Deletion differences are 
        // are found and added to the main list of Differences.

        public void recurseDirectoryNode(TreeNode N, Pair<DirectionType, OperationType> f, int systemID)
        {
            List<int> fileChilds, folderChilds;
            String newAddress;
            int nodeParent = N.getId();

            fileChilds = treeHandler.getFileChilds(N);
            folderChilds = treeHandler.getFolderChilds(N);

            if (systemName.CompareTo(SystemType.Host) == 0)
            {
                for (int j = 0; j < fileChilds.Count(); j++)
                {
                    newAddress = Path.Combine(arrNode[nodeParent].getFullPathA(), arrNode[fileChilds[j]].getName());
                    arrNode[fileChilds[j]].setFullPathA(newAddress);
                    if (systemID == 0 && f.Second!=OperationType.None)
                    {                        
                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[j]].getFullPathA()), arrNode[fileChilds[j]], f));
                    }
                    else if (f.Second != OperationType.None)
                    {                        
                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[j]].getFullPathB()), arrNode[fileChilds[j]], f));
                    }
                }

                for (int j = 0; j < folderChilds.Count(); j++)
                {
                    newAddress = Path.Combine(arrNode[nodeParent].getFullPathA(), arrNode[folderChilds[j]].getName());
                    arrNode[folderChilds[j]].setFullPathA(newAddress);
                    if (systemID == 0 && f.Second!=OperationType.None)
                        AddDifference(new Difference(new DirectoryInfo(arrNode[folderChilds[j]].getFullPathA()), arrNode[folderChilds[j]], f));
                    else if (f.Second != OperationType.None)
                        AddDifference(new Difference(new DirectoryInfo(arrNode[folderChilds[j]].getFullPathB()), arrNode[folderChilds[j]], f));

                    recurseDirectoryNode(arrNode[folderChilds[j]], f, systemID);    //Recurse here
                }
            }

            else if (systemName.CompareTo(SystemType.Guest) == 0)
            {
                for (int j = 0; j < fileChilds.Count(); j++)
                {
                    newAddress = Path.Combine(arrNode[nodeParent].getFullPathB(), arrNode[fileChilds[j]].getName());
                    arrNode[fileChilds[j]].setFullPathB(newAddress);
                    if (systemID == 0 && f.Second != OperationType.None)
                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[j]].getFullPathA()), arrNode[fileChilds[j]], f));
                    else if (f.Second != OperationType.None)
                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[j]].getFullPathB()), arrNode[fileChilds[j]], f));
                }

                for (int j = 0; j < folderChilds.Count(); j++)
                {
                    newAddress = Path.Combine(arrNode[nodeParent].getFullPathB(), arrNode[folderChilds[j]].getName());
                    arrNode[folderChilds[j]].setFullPathB(newAddress);
                    if (systemID == 0 && f.Second != OperationType.None)
                        AddDifference(new Difference(new DirectoryInfo(arrNode[folderChilds[j]].getFullPathA()), arrNode[folderChilds[j]], f));
                    else if (f.Second != OperationType.None)
                        AddDifference(new Difference(new DirectoryInfo(arrNode[folderChilds[j]].getFullPathB()), arrNode[folderChilds[j]], f));

                    recurseDirectoryNode(arrNode[folderChilds[j]], f, systemID);    //Recurse here
                }
            }
        }

        /// <summary>
        /// Hard disk stores file without the slashes and colon so that there will be a unique storing scheme
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private String StripString(String input)
        {
            String[] arrWords;
            String concat = "";
            arrWords = input.Split('\\', ':');
            foreach (String str in arrWords)
            {
                concat += str;
            }
            return concat;
        }
    }

    /******************************/
    /* Partial Class Analyze Ends */
    /******************************/
}
